1 module unde.games.obj_loader;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 import std.exception;
7 import std.path;
8 import std.stdio;
9 
10 import std.string;
11 struct MtlMaterial
12 {
13     string name;
14     string map_diffuse;
15     string map_bump;
16     float[3] ambient;
17     float[3] diffuse;
18     float[3] specular;
19     float[3] emissive;
20     float specular_koef;
21     float optical_density;
22     float transparency;
23     int illum_model;
24 }
25 
26 struct MtlFile
27 {
28     string filename;
29     MtlMaterial*[string] materials;
30 }
31 
32 struct ObjIndex
33 {
34     int vert = -1;
35     int tex = -1;
36     int normal = -1;
37 }
38 
39 struct ObjMesh
40 {
41     string material;
42     bool smooth;
43     ObjIndex*[][] faces;
44 }
45 
46 struct ObjObject
47 {
48     string name;
49     float[3][] vertices;
50     float[2][] texcoords;
51     float[3][] normals;
52 
53     ObjMesh*[] meshes;
54 }
55 
56 struct ObjFile
57 {
58     string filename;
59     MtlFile *mtl;
60     ObjObject*[] objects;
61 }
62 
63 private MtlFile*[string] mtl_files;
64 
65 private float[3] get_3f(char[] str)
66 {
67     return str.splitter(" ").map!(a => a.to!(float)())().array[0..3];
68 }
69 
70 private float[2] get_2f(char[] str)
71 {
72     return str.splitter(" ").map!(a => a.to!(float)())().array[0..2];
73 }
74 
75 private int get_index(char[] str)
76 {
77     if (str == "") return -1;
78     else return str.to!(int);
79 }
80 
81 private ObjIndex* get_indices(char[] str, int[3] offsets)
82 {
83     int[] numbers = str.splitter("/").map!(a => a.get_index())().array;
84     if (numbers.length < 1) numbers ~= -1;
85     if (numbers.length < 2) numbers ~= -1;
86     if (numbers.length < 3) numbers ~= -1;
87     numbers[] -= offsets[];
88     return new ObjIndex(numbers[0]-1, numbers[1]-1, numbers[2]-1);
89 }
90 
91 private ObjIndex*[] get_face(char[] str, int[3] offsets)
92 {
93     return str.splitter(" ").map!(a => a.get_indices(offsets))().array[0..$];
94 }
95 
96 MtlFile *load_mtlfile(string filename)
97 {
98     MtlFile *mtl = new MtlFile;
99     mtl.filename = filename;
100 
101     string mat;
102 
103     auto file = File(filename);
104     foreach (line; file.byLine())
105     {
106         if (line == "" || line[0] == '#')
107             continue;
108         else if (line.startsWith("Ns "))
109         {
110             mtl.materials[mat].specular_koef = line[3..$].to!(float);
111         }
112         else if (line.startsWith("Ka "))
113         {
114             mtl.materials[mat].ambient = get_3f(line[3..$]);
115         }
116         else if (line.startsWith("Kd "))
117         {
118             mtl.materials[mat].diffuse = get_3f(line[3..$]);
119         }
120         else if (line.startsWith("Ks "))
121         {
122             mtl.materials[mat].specular = get_3f(line[3..$]);
123         }
124         else if (line.startsWith("Ke "))
125         {
126             mtl.materials[mat].emissive = get_3f(line[3..$]);
127         }
128         else if (line.startsWith("Ni "))
129         {
130             mtl.materials[mat].optical_density = line[3..$].to!(float);
131         }
132         else if (line.startsWith("d "))
133         {
134             mtl.materials[mat].transparency = line[2..$].to!(float);
135         }
136         else if (line.startsWith("map_Kd "))
137         {
138             mtl.materials[mat].map_diffuse = line[7..$].idup();
139         }
140         else if (line.startsWith("map_d "))
141         {
142             // Just ignore
143         }
144         else if (line.startsWith("illum "))
145         {
146             mtl.materials[mat].illum_model = line[6..$].to!(int);
147         }
148         else if (line.startsWith("newmtl "))
149         {
150             mat = line[7..$].idup();
151             mtl.materials[mat] = new MtlMaterial;
152         }
153         else if (line.startsWith("map_Bump "))
154         {
155             mtl.materials[mat].map_diffuse = line[9..$].idup();
156         }
157         else
158         {
159             throw new Exception("Cannot parse mtl line: "~line.idup());
160         }
161     }
162 
163     return mtl;
164 }
165 
166 ObjFile *load_objfile(string filename)
167 {
168     ObjFile *obj = new ObjFile;
169     obj.filename = filename;
170 
171     int[3] offsets;
172     try 
173     {
174         auto file = File(filename);
175         foreach (line; file.byLine())
176         {
177             if (line == "" || line[0] == '#')
178                 continue;
179             else if (line.startsWith("o "))
180             {
181                 if (obj.objects.length > 0)
182                 {
183                     offsets[0] += obj.objects[$-1].vertices.length;
184                     offsets[1] += obj.objects[$-1].texcoords.length;
185                     offsets[2] += obj.objects[$-1].normals.length;
186                 }
187                 
188                 obj.objects ~= new ObjObject;
189                 obj.objects[$-1].name = line[2..$].idup();
190             }
191             else if (line.startsWith("v "))
192             {
193                 obj.objects[$-1].vertices ~= get_3f(line[2..$]);
194             }
195             else if (line.startsWith("vt "))
196             {
197                 obj.objects[$-1].texcoords ~= get_2f(line[3..$]);
198             }
199             else if (line.startsWith("vn "))
200             {
201                 obj.objects[$-1].normals ~= get_3f(line[3..$]);
202             }
203             else if (line.startsWith("s "))
204             {
205                 string material;
206                 bool n = true;
207                 if (obj.objects[$-1].meshes.length > 0)
208                 {
209                     material = obj.objects[$-1].meshes[$-1].material;
210                     n = obj.objects[$-1].meshes[$-1].faces.length > 0;
211                 }
212                 
213                 if (n)
214                 {
215                     obj.objects[$-1].meshes ~= new ObjMesh;
216                     obj.objects[$-1].meshes[$-1].material = material;
217                 }
218                 
219                 obj.objects[$-1].meshes[$-1].smooth = (line[2..$] == "1");
220             }
221             else if (line.startsWith("p ") || line.startsWith("l ") || line.startsWith("f "))
222             {
223                 obj.objects[$-1].meshes[$-1].faces ~= get_face(line[2..$], offsets);
224             }
225             else if (line.startsWith("usemtl "))
226             {
227                 obj.objects[$-1].meshes ~= new ObjMesh;
228                 obj.objects[$-1].meshes[$-1].material = line[7..$].idup();
229             }
230             else if (line.startsWith("mtllib "))
231             {
232                 string mtlfile = line[7..$].idup();
233                 
234                 if (!isAbsolute(mtlfile))
235                 {
236                     mtlfile = chainPath(dirName(filename), mtlfile).array;
237                 }
238                 
239                 if (mtlfile !in mtl_files)
240                 {
241                     mtl_files[mtlfile] = load_mtlfile(mtlfile);
242                 }
243                 
244                 obj.mtl = mtl_files[mtlfile];
245             }
246             else
247             {
248                 throw new Exception("Cannot parse obj line: "~line.idup());
249             }
250         }
251     }
252     catch (ErrnoException err)
253     {
254         writefln("%s", err);
255     }
256 
257     return obj;    
258 }